<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<!-- 添加viewport标签确保移动端正确缩放 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI-聊天模式</title>
<script src="jquery.min.js"></script>
<style>
	body {
		font-family: 'Segoe UI', Arial, sans-serif;
		background: #f0f2f5;
		margin: 0;
		min-height: 100vh;
		/* 改为min-height避免内容溢出 */
		display: flex;
		justify-content: center;
		align-items: center;
	}
	.container {
		width: 95vw;
		/* 增加宽度占比 */
		height: 95vh;
		display: flex;
		flex-direction: column;
		gap: 10px;
		/* 缩小间隙 */
		padding: 5px;
		/* 添加内边距 */
	}
	#output {
		flex: 1;
		width: calc(100% - 10px);
		/* 考虑内边距 */
		min-height: 50%;
		padding: 12px;
		font-size: 16px;
		/* 增大字体 */
		border: 2px solid #e3e8ee;
		border-radius: 6px;
		background: white;
		resize: none;
		overflow-y: auto;
		/* 确保滚动条可用 */
	}
	.input-group {
		display: flex;
		flex-direction: column;
		/* 改为垂直布局 */
		gap: 8px;
		height: auto;
		/* 自动高度 */
	}
	#input {
		width: calc(100% - 10px);
		min-height: 80px;
		/* 更适合移动端的高度 */
		padding: 10px;
		font-size: 16px;
		border: 2px solid #e3e8ee;
		border-radius: 6px;
		resize: vertical;
		/* 允许垂直调整 */
	}
	button {
		padding: 12px 20px;
		background: #007bff;
		color: white;
		border: none;
		border-radius: 6px;
		font-size: 16px;
		cursor: pointer;
		transition: background 0.2s;
		touch-action: manipulation;
		/* 优化触摸响应 */
	}
	/* 新增按钮容器样式 */
	.button-row {
		display: flex;
		gap: 8px;
		width: 100%;
	}
	/* 发送按钮宽度设置 */
	button[onclick="sendMessage()"] {
		flex: 1;
		/* 占据剩余空间 */
		width: 80%;
	}
	button:active {
		background: #0056b3;
	}
	/* 新增图标按钮样式 */
	button.icon-button {
		padding: 12px;
		width: 20%;
		min-width: 60px;
		background: #28a745;
		display: flex;
		justify-content: center;
		align-items: center;
	}
	/* 调整按钮组间距 */
	.button-group {
		display: flex;
		gap: 8px;
		margin-top: 8px;
	}
	.icon-button {
		position: relative;
	}
	/* 喇叭图标样式 */
	.icon-button svg {
		width: 24px;
		height: 24px;
		fill: white;
		transition: opacity 0.3s;
	}
	.icon-button .off-icon {
		position: absolute;
		opacity: 0;
	}
	/* 激活状态 */
	.icon-button.active .on-icon {
		opacity: 0;
	}
	.icon-button.active .off-icon {
		opacity: 1;
	}
	/* 颜色变化 */
	.icon-button.active {
		background: #dc3545;
	}
	/* 手机端响应式调整 */
	@media (max-width: 480px) {
		.container {
			width: 100vw;
			height: 100vh;
			padding: 5px;
		}
		#output {
			font-size: 15px;
			padding: 10px;
		}
		#input {
			font-size: 15px;
			min-height: 70px;
		}
		button {
			padding: 15px 20px;
			/* 增大点击区域 */
			font-size: 15px;
		}
		button.icon-button {
			padding: 10px;
			width: 44px;
		}
		.icon-button svg {
			width: 22px;
			height: 22px;
		}
		.file-input-button {
			padding: 10px;
			height: 50px;
			border-radius: 4px;
		}
		.file-input-button svg {
			width: 22px;
			height: 22px;
		}
	}
	/* 高对比度模式适配 */
	@media (prefers-contrast: more) {
		.file-input-button {
			border: 2px solid currentColor;
		}
	}
	#fileInput {
		position: absolute;
		opacity: 0;
		width: 100%;
		left: 0;
		top: 0;
		width: 100%;
		height: 100%;
		cursor: pointer;
		z-index: 1;
		-webkit-tap-highlight-color: transparent;
	}
	.file-input-wrapper {
		position: relative;
		display: inline-block;
		width: 80px;height:50px;
		touch-action: manipulation;
	}
	.file-input-button {
		padding: 12px;
		background: #6c757d;
		color: white;
		border-radius: 6px;
		position: relative;
		z-index: 0;
		display: flex;
		justify-content: center;
		align-items: center;
		width: 100%;
		border: 1px solid #dee2e6;
		transition:
			background 0.2s,
			border-color 0.2s;/
	}
	.file-input-button:active {
		background: #5a6268;
		border-color: #5a6268;
	}
	.file-input-wrapper:active .file-input-button {
		transform: scale(0.98);
	}
	.file-input-button svg {
		width: 24px;
		height: 24px;
		fill: white;
	}
	.file-input-button:hover {
		background: #5a6268;
	}
	/* 禁用状态 */
	#fileInput:disabled+.file-input-button {
		opacity: 0.6;
		pointer-events: none;
	}
	/* 新增的 Tab 按钮样式 */
	.tab-buttons {
		display: flex;
		gap: 6px;
		margin-bottom: 12px;
		border-bottom: 2px solid #e3e8ee;
		padding-bottom: 8px;
	}
	.tab-button {
		padding: 8px 10px;
		border: none;
		border-radius: 4px 4px 0 0;
		background: #f0f2f5;
		color: #495057;
		font-size: 14px;
		cursor: pointer;
		transition: all 0.2s;
		position: relative;
		bottom: -2px;
	}
	.tab-button.active {
		background: white;
		color: #007bff;
		border-bottom: 3px solid #007bff;
		font-weight: 500;
	}
	.tab-button:hover:not(.active) {
		background: #e9ecef;
		color: #212529;
	}
    .chaturl{width:140px;}
    .chatmodel{width:100px;}
</style>
</head>
<body>
<div class="container">
	<textarea id="output" readonly placeholder="结果将显示在这里..."></textarea>
	<!-- 按钮容器 -->
	<div class="tab-buttons">
		<button id="chat" class="tab-button active" data-target="chat"> 聊天</button>
		<button id="chat" class="tab-button" data-target="generate"> 文本</button>
		<input type="text" class="chaturl" value="" onchange="init()" />
        <select id="model" class="chatmodel" onchange="changemodel()">
        </select>
	</div>
	<div class="input-group">
		<textarea id="input" rows="2" placeholder="输入命令（/clear 清空）Shift+Enter换行"></textarea>
		<div class="button-row">
			<button onclick="sendMessage()">发送</button>
			<button class="icon-button" onclick="playSound(this)">
				<svg class="on-icon" viewBox="0 0 24 24">
					<path fill="currentColor"
						d="M15 3v18l-5-4H4V7h6l5-4zm3.5 5.5c1-1 2.5-1.5 4-1.5v3c-.6 0-1.2.2-1.7.5l-2.3-2zm2.3 7.7c.8-.6 1.5-1.5 1.9-2.7h-3c-.1.5-.3 1-.7 1.4l1.8 1.3z" />
				</svg>
				<svg class="off-icon" viewBox="0 0 24 24">
					<path fill="currentColor"
						d="M15 3v18l-5-4H4V7h6l5-4zm7.1 14.7l-1.4-1.4-3.6-3.6-3.6 3.6-1.4-1.4 3.6-3.6-3.6-3.6 1.4-1.4 3.6 3.6 3.6-3.6 1.4 1.4-3.6 3.6 3.6 3.6z" />
				</svg>
			</button>
			<div class="file-input-wrapper">
				<button class="file-input-button">
					<svg viewBox="0 0 24 24">
						<path fill="currentColor"
							d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11zm-3-9H7v-2h8v2zm-8 4h8v-2H7v2z" />
					</svg>
				</button>
				<input type="file" id="fileInput" accept=".txt" />
			</div>
		</div>
	</div>
</div>
<script>
    let baseURL,baseModel;
    var audio;
    var requesttype = 1;//1直连 2代理中转
    // 公共代理前缀
    const proxyUrl = "https://cors-proxy.bitefu.net/";
	init();
	const outputDiv = document.getElementById('output');
	let isSpeaking = false;
	let isSending = null;
	let currentUtterance = null;
	let fullText = null;
    function init(){
        if (localStorage.getItem('baseURL') === null) {
    		localStorage.setItem('baseURL', 'http://120.232.79.82:11434');
    	}
    	if (localStorage.getItem('baseModel') === null) {
    		localStorage.setItem('baseModel', 'qwen2.5:14b');
    	}
    	baseURL = localStorage.getItem('baseURL');
    	baseModel = localStorage.getItem('baseModel');
        var oldurl = $('.chaturl').val();
        if(!oldurl){
            $('.chaturl').val(baseURL);
            oldurl=baseURL;
        }
        const urlRegex =
				/^https?:\/\/((1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.){3}(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)(:\d+)?(\/.*)?$/;
		if (!urlRegex.test(oldurl.trim())) {
			alert('请按下面格式输入。\n输入命令(/url http://127.0.0.1:11434)');return;
		}
        if(oldurl!=baseURL){
            localStorage.setItem('baseURL', oldurl);
    		baseURL = oldurl;
            fetchModels();
        }else{
            var oldmodel = $('.chatmodel').val();//console.log(oldmodel);
            if(!oldmodel || oldmodel==''){
                fetchModels();
            }else if(oldmodel!=baseModel){
                choosemodel(baseModel);
            }
        }
    }
    function choosemodel(name){
        var selectElement= $('.chatmodel');
        var optionExists = selectElement.find('option[value="' + name + '"]').length > 0;
        if(optionExists){
            selectElement.val(name);
        }else{
            selectElement.append($('<option>', {
                value: name,
                text: name,
                selected: true // 直接在创建时设置选中状态
            }));
        }
    }
    function changemodel(){
        var selectvalue= $('.chatmodel').val();
        localStorage.setItem('baseModel', selectvalue);
		baseModel = selectvalue;//console.log(baseModel);
    }
	document.addEventListener("DOMContentLoaded", function() {
		document.addEventListener("keydown", function(event) {
			if (event.key === "Enter") {
				sendMessage();
				event.preventDefault();
			}
		});
	});
	let targetId = 'chat';
	const buttons = document.querySelectorAll('.tab-button');
	buttons.forEach(button => {
		button.addEventListener('click', function() {
			buttons.forEach(btn => btn.classList.remove('active'));
			this.classList.add('active');
			targetId = this.dataset.target;
			document.title = `AI-${targetId === 'chat' ? "聊天" : "查询"}模式`;
			console.log('当前选项卡:', targetId);
			document.querySelectorAll('.content').forEach(content => {
				content.classList.remove('active');
			});
			if (isSending === null) {
				document.getElementById(targetId).classList.add('active');
				outputDiv.value = '';
				if (targetId === 'chat') {
					let i = 1;
					while (i < messages.length) {
						outputDiv.value +=
							`\n\n您：${messages[i]['content']}\n\nAI：${messages[i+1]['content']}`;
						i += 2;
					}
					outputDiv.scrollTop = outputDiv.scrollHeight;
				}
			}
		});
	});
	// 可选：添加键盘导航支持Alt+1
	document.addEventListener('keydown', (e) => {
		if (e.altKey) {
			if (e.key === '1') document.querySelector('[data-target="chat"]').click();
			if (e.key === '2') document.querySelector('[data-target="generate"]').click();
		}
	});
	function playSound(btn) {
		return new Promise((resolve) => { // 返回 Promise
			const content = outputDiv.value.split('AI：');
			const aiText = content[content.length - 1]?.trim() || '';
			if (!isSpeaking) {
				if (aiText) {
				    playAudio(aiText);
					btn.classList.toggle('active');
					resolve(); // 异步完成，通知外部
				} else {
					resolve(); // 无内容时直接 resolve
				}
			} else {
				pauseAudio()
				isSpeaking = false;
				btn.classList.toggle('active');
				resolve(); // 异步完成，通知外部
			}
		});
	}
    function playAudio(txt) {
        if (isSpeaking) return; // 如果已经在播放，则不执行任何操作
        if (!audio.paused) { // 检查是否已暂停
            audio.pause(); // 如果音频正在播放，则暂停它
        } 
        var query={txt:txt};
        $.ajax({
          url: 'tts.php',
          type: "post",
          data: query,
          dataType: "json",
          success: function (ret) {
            if(ret.mp3){
                audio = new Audio(ret.mp3);
                
                audio.play().then(() => {
                    // 播放成功
                    console.log('播放成功');
                    isSpeaking = true;
                }).catch(error => {
                    // 自动播放被浏览器阻止，提示用户点击播放按钮
                    console.error('自动播放被阻止', error);
                    alert('请点击播放按钮开始播放');
                });
            }else{
                if(!ret.msg)ret.msg='查询失败';
                alert(ret.msg);
            }
            if(ret.url)setTimeout(function(){location.href=ret.url;},1000);
            return false;
          }
        });
    }
    function pauseAudio() {
        audio.pause();
        isSpeaking = false; // 更新状态为未播放
    }
	// 添加 HTML 转义函数
	const sanitizeHTML = (str) => {
		const div = document.createElement('div');
		div.textContent = str.replace(/\s/g, '');
		return div.innerHTML;
	};
	let data = null;
	let messages = [{
		role: "system",
		content: "You are a warm-hearted assistant, and you only speak Chinese."
	}];
	if (localStorage.getItem('messages') !== null) {
		messages = JSON.parse(localStorage.getItem("messages")) || [];
		document.querySelector('[data-target="chat"]').click();
	}
    async function fetchModels() {
        
        // 原始API URL
        const targetUrl =  `${baseURL}/api/tags`;
        // 组合后的URL
        const url = requesttype==2?proxyUrl+''+ targetUrl:targetUrl;
        
        const response =await fetch(url, {
            // headers: {
            //     'Authorization': `Bearer ${API_KEY}`
            // }
        });
        var selectElement= $('.chatmodel');
        const data = await response.json();
        const models = data.models;
        var html='';
        for(var i in models){
            var model = models[i];
            if(baseModel && baseModel==model.name) {
                html+='<option value="'+model.name+'" selected>'+model.name+'</option>';
            }else{
                html+='<option value="'+model.name+'">'+model.name+'</option>';
            }
        }
        if(!baseModel){
            localStorage.setItem('baseModel', models[0].name);
			baseModel = localStorage.getItem('baseModel');
        }
        selectElement.html(html);
    }
	function sendMessage() {
		let input = document.getElementById('input').value;
		document.getElementById('input').value = '';
		if (isSending) {
			alert('请勿打扰，思考中。。。。。。');
			return;
		}
		if (input.trim() === '/clear') {
			outputDiv.value = '';
			messages.splice(1);
			localStorage.setItem("messages", JSON.stringify(messages));
			fullText = '';
			return;
			//localStorage.removeItem('user_theme');
			//localStorage.clear();
		}
		if (input.trim().length === 0) {
			alert('输入为空！');
			return;
		}
		if (input.trim().slice(0, 1) === '/' || input.trim() === '/help') {
			alert('下面是常用命令。\n清空 /clear');
			return;
		}
		outputDiv.value += `\n\n您：${sanitizeHTML(input)}\n\nAI：\n`;
		outputDiv.scrollTop = outputDiv.scrollHeight;
		isSending = targetId;console.log(baseModel);
		if (isSending === 'chat') {
			if (typeof fullText === 'string' && fullText.trim().length > 0) {
				input = `基于以下上下文：\n${fullText}\n\n请回答：${input}`;
				//console.log(input);
			}
			messages.push({
				role: "user",
				content: input
			});
			data = {
				model: baseModel,
				messages: messages,
				stream: true
			};
		} else {
			if (typeof fullText === 'string' && fullText.trim().length > 0) {
				input = `基于以下上下文：\n${fullText}\n\n请回答：${input}`;
				console.log(input);
			}
			data = {
				model: baseModel,
				prompt: input
			};
		}// 原始API URL
		const targetUrl = `${baseURL}/api/${isSending}`;
        
        // 组合后的URL
        const url =requesttype==2? proxyUrl+''+ targetUrl:targetUrl;
		console.log(url);
		fetch(url, {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify(data),
				mode: 'cors'
			})
			.then(response => {
				if (!response.ok) throw new Error(`HTTP错误: ${response.status}`);
				const reader = response.body.getReader();
				const decoder = new TextDecoder('utf-8');
				let prevChunk = '';
				function processChunk({
					done,
					value
				}) {
					if (done) {
						// 处理流结束后的剩余数据
						if (prevChunk) {
							try {
								const parsed = JSON.parse(prevChunk);
								appendToOutput(`${targetId === 'chat' ? parsed.message.content : parsed.response}`);
							} catch (e) {
								appendToOutput(`解析错误: ${e.message}`, true);
							}
						}
						return; // 结束递归
					}
					const chunk = decoder.decode(value, {
						stream: true
					});
					prevChunk += chunk;
					const lines = prevChunk.split('\n');
					prevChunk = lines.pop() || ''; // 保存未完成的行
					for (const line of lines) {
						try {
							const parsed = JSON.parse(line);
							appendToOutput(`${isSending === 'chat' ? parsed.message.content : parsed.response}`);
						} catch (e) {
							appendToOutput(`解析错误: ${e.message}`, true);
						}
					}
					// 继续读取下一个chunk
					return reader.read().then(processChunk);
				}
				// 辅助函数：更新界面并滚动
				function appendToOutput(tep_mesg, isError = false) {
                    var output='';
                    if (tep_mesg == '\u003cthink\u003e') {
                        output += "思考开始"
                    } else if (tep_mesg == '\u003c/think\u003e') {
                        output += "思考结束"
                    }
                    else {
                        output += tep_mesg;
                    }
                    //console.log(output);
					outputDiv.value += output + (isError ? '\n' : '');
					outputDiv.scrollTop = outputDiv.scrollHeight;
				}
				// 开始读取流
				return reader.read().then(processChunk);
			})
			.then(() => {
				console.log('流式处理完成');
				if (targetId === 'chat') {
					if (outputDiv.value.trim()) {
						const content = outputDiv.value.split('AI：');
						messages.push({
							role: "assistant",
							content: content[content.length - 1]
						});
						if (messages.length > 21) messages.splice(1, 2);
					}
					localStorage.setItem("messages", JSON.stringify(messages));
				}
				isSending = null;
			})
			.catch(error => {
				messages.splice(-1, 1);
				outputDiv.value += `错误：${error.message}\n`;
				outputDiv.scrollTop = outputDiv.scrollHeight;
				isSending = null;
			});
	}
	// 添加到JavaScript中
	document.getElementById('fileInput').addEventListener('change', function(e) {
		const file = event.target.files[0];
		if (file && file.name.endsWith('.txt')) {
			fileName = file.name;
			const reader = new FileReader();
			reader.readAsText(file, 'UTF-8');
			reader.onload = (e) => {
				const utf8Text = e.target.result;
				if (!/[\\u4E00-\\u9FA5]/.test(utf8Text)) { // 检测是否存在中文
					reader.readAsText(file, 'GBK'); // 尝试 GBK 编码
					reader.onload = (e) => {
						splitText(e);
					};
				} else {
					splitText(e);
				}
			};
		} else {
			alert('请选择一个有效的 .txt 文件。');
		}
	});
	function splitText(e) {
		fullText = e.target.result;
		console.log(fullText);
	}
</script>
</body>
</html>